﻿
#引入包
import math
from math import floor, pi, log, tan, atan, exp
from threading import Thread, Lock
from geopy.geocoders import Nominatim
import urllib.request as ur
import PIL.Image as pillow
import io
import random

MAP_URLS = {
    "google": "http://mt2.google.cn/vt/lyrs={style}&hl=zh-CN&gl=CN&src=app&x={x}&y={y}&z={z}",
    "amap": "http://wprd02.is.autonavi.com/appmaptile?style={style}&x={x}&y={y}&z={z}",
    "tencent_s": "http://p3.map.gtimg.com/sateTiles/{z}/{fx}/{fy}/{x}_{y}.jpg",
    "tencent_m": "http://rt0.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=3"  }

agents = [
    'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5',
    'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7',
    'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27',
    'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1']

COUNT=0
mutex=Lock()

def transformLat(x, y):
    ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0
    return ret

def transformLon(x, y):
    ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
    ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0
    return ret

def delta(lat, lon):
    a = 6378245.0   
    ee = 0.00669342162296594323  
    dLat = transformLat(lon - 105.0, lat - 35.0)
    dLon = transformLon(lon - 105.0, lat - 35.0)
    radLat = lat / 180.0 * math.pi
    magic = math.sin(radLat)
    magic = 1 - ee * magic * magic
    sqrtMagic = math.sqrt(magic)
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * math.pi)
    dLon = (dLon * 180.0) / (a / sqrtMagic * math.cos(radLat) * math.pi)
    return {'lat': dLat, 'lon': dLon}

def outOfChina(lat, lon):
    if (lon < 72.004 or lon > 137.8347):
        return True
    if (lat < 0.8293 or lat > 55.8271):
        return True
    return False

def gcj_to_wgs(gcjLon,gcjLat):
    if outOfChina(gcjLat, gcjLon):
        return (gcjLon, gcjLat)
    d = delta(gcjLat, gcjLon)
    return (gcjLon - d["lon"],gcjLat - d["lat"])

def wgs_to_gcj(wgsLon,wgsLat):
    if outOfChina(wgsLat, wgsLon):
        return wgsLon, wgsLat
    d = delta(wgsLat, wgsLon);
    return wgsLon + d["lon"], wgsLat + d["lat"]

def wgs_to_macator(x, y):
    y = 85.0511287798 if y > 85.0511287798 else y
    y = -85.0511287798 if y < -85.0511287798 else y

    x2 = x * 20037508.34 / 180
    y2 = log(tan((90 + y) * pi / 360)) / (pi / 180)
    y2 = y2 * 20037508.34 / 180
    return x2, y2

def mecator_to_wgs(x, y):
    x2 = x / 20037508.34 * 180
    y2 = y / 20037508.34 * 180
    y2 = 180 / pi * (2 * atan(exp(y2 * pi / 180)) - pi / 2)
    return x2, y2

def wgs84_to_tile(j, w, z):
    isnum = lambda x: isinstance(x, int) or isinstance(x, float)
    if not(isnum(j) and isnum(w)):
        raise TypeError("j and w must be int or float!")

    if not isinstance(z, int) or z < 0 or z > 22:
        raise TypeError("z must be int and between 0 to 22.")

    if j < 0:
        j = 180 + j
    else:
        j += 180
    j /= 360  # make j to (0,1)

    w = 85.0511287798 if w > 85.0511287798 else w
    w = -85.0511287798 if w < -85.0511287798 else w
    w = log(tan((90 + w) * pi / 360)) / (pi / 180)
    w /= 180  # make w to (-1,1)
    w = 1 - (w + 1) / 2  # make w to (0,1) and left top is 0-point

    num = 2**z
    x = floor(j * num)
    y = floor(w * num)
    return x, y

def tileframe_to_mecatorframe(zb):
    inx, iny =zb["LT"]   
    inx2,iny2=zb["RB"]   
    length = 20037508.3427892
    sum = 2**zb["z"]
    LTx = inx / sum * length * 2 - length
    LTy = -(iny / sum * length * 2) + length

    RBx = (inx2 + 1) / sum * length * 2 - length
    RBy = -((iny2 + 1) / sum * length * 2) + length

    res = {'LT': (LTx, LTy), 'RB': (RBx, RBy),
           'LB': (LTx, RBy), 'RT': (RBx, LTy)}
    return res

def tileframe_to_pixframe(zb):

    out={}
    width=(zb["RT"][0]-zb["LT"][0]+1)*256
    height=(zb["LB"][1]-zb["LT"][1]+1)*256
    out["LT"]=(0,0)
    out["RT"]=(width,0)
    out["LB"]=(0,-height)
    out["RB"]=(width,-height)
    return out

class Downloader(Thread):
    def __init__(self,index,count,urls,datas,update):
        super().__init__()
        self.urls=urls
        self.datas=datas
        self.index=index
        self.count=count
        self.update=update

    def download(self,url):
        HEADERS = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36'}
        header = ur.Request(url)
        header.add_header('User-Agent', random.choice(agents)) 
        err=0
        while(err<3):
            try:
                data = ur.urlopen(header, timeout=60).read()
            except:
                err+=1
            else:
                return data
        raise Exception("Ê§Ð§µÄÍøÂçÁ´½Ó.")

    def run(self):
        for i,url in enumerate(self.urls):
            if i%self.count != self.index:
                continue
            self.datas[i]=self.download(url)
            if mutex.acquire():
                self.update()
                mutex.release()

def geturl(source, x, y, z, style):
    if source == 'google':
        furl = MAP_URLS["google"].format(x=x, y=y, z=z, style=style)
    elif source == 'amap':
        style = 6 if style == 's' else 7
        furl = MAP_URLS["amap"].format(x=x, y=y, z=z, style=style)
    elif source == 'tencent':
        y = 2**z - 1 - y
        if style == 's':
            furl = MAP_URLS["tencent_s"].format(
                x=x, y=y, z=z, fx=floor(x / 16), fy=floor(y / 16))
        else:
            furl = MAP_URLS["tencent_m"].format(x=x, y=y, z=z)
    else:
        raise Exception("未知地图源! ")

    return furl

def downpics(urls,multi=10):

    def makeupdate(s):
        def up():
            global COUNT
            COUNT+=1
            print("\n正在下载 ... [{0}/{1}]".format(COUNT,s),end='')
        return up

    url_len=len(urls)
    datas=[None] * url_len
    if multi <1 or multi >16 or not isinstance(multi,int):
        raise Exception("线程请保持在 [0,20] .")
    tasks=[Downloader(i,multi,urls,datas,makeupdate(url_len)) for i in range(multi)]
    for i in tasks:
        i.start()
    for i in tasks:
        i.join()

    return datas


def getpic(x1, y1, x2, y2, z, source='google', outfile="MAP_OUT.png", style='s'):
    pos1x, pos1y = wgs84_to_tile(x1, y1, z)
    pos2x, pos2y = wgs84_to_tile(x2, y2, z)
    lenx = pos2x - pos1x + 1
    leny = pos2y - pos1y + 1
    print("总数：{x} X {y}".format(x=lenx, y=leny))

    urls = [geturl(source, i, j, z, style) for j in range(pos1y, pos1y + leny) for i in range(pos1x, pos1x + lenx)]

    datas = downpics(urls)

    print("\n 正在拼接..")
    outpic = pillow.new('RGB', (lenx * 256, leny * 256))
    for i, data in enumerate(datas):
        picio = io.BytesIO(data)
        small_pic = pillow.open(picio)

        y, x = i // lenx, i % lenx
        outpic.paste(small_pic, (x * 256, y * 256))

    print('保存中..')
    outpic.save(outfile)
    print('输出完成')
    return {"LT":(pos1x,pos1y),"RT":(pos2x,pos1y),"LB":(pos1x,pos2y),"RB":(pos2x,pos2y),"z":z}



def screen_out(zb,name):
    if not zb:
        print("N/A")
        return
    print("名称：",name)
    print("左上角({0:.5f},{1:.5f})".format(*zb['LT']))
    print("右上角({0:.5f},{1:.5f})".format(*zb['RT']))
    print("左下角({0:.5f},{1:.5f})".format(*zb['LB']))
    print("右上角({0:.5f},{1:.5f})".format(*zb['RB']))


def file_out(zb,file,target="keep",output="file"):
    pixframe=tileframe_to_pixframe(zb)
    Xframe=tileframe_to_mecatorframe(zb)
    for i in ["LT","LB","RT","RB"]:
        Xframe[i]=mecator_to_wgs(*Xframe[i])
    if target =="keep":
        pass;
    elif target == "gcj":
            for i in ["LT","LB","RT","RB"]:
                Xframe[i]=wgs_to_gcj(*Xframe[i])
    elif target == "wgs":
            for i in ["LT","LB","RT","RB"]:
                Xframe[i]=gcj_to_wgs(*Xframe[i])
    else:
        raise Exception("Invalid argument: target.")
    
    if output=="file":
        f=open(file,"w")
        for i in ["LT","LB","RT","RB"]:
            f.write("{0[0]:.5f}, {0[1]:.5f}, {1[0]:.5f}, {1[1]:.5f} \n".format(pixframe[i], Xframe[i]))#pixframe[i]
        f.close()
        print("完成 ",file)
    else:
        screen_out(Xframe,target)
maps = {
        "1":["google","m"],
        "2":["google","s"],
        "3":["amap","6"],
        "4":["amap","7"],
        "5":["tencent","s"],
        "6":["tencent","m"]
        }
print (" ===================\n",
       "互联网地图下载工具\n",
       "===================\n",
       )
leftTop = input("请输入左上角坐标：")
rightBottom = input("请输入右下角坐标：")
zoomLevel = input("请输入缩放级别：")
print (
        " ===================\n",
        "1. 谷歌地图\n",
        "2. 谷歌影像\n",
        "3. 高德地图\n",
        "4. 高德影像\n",
        "5. 腾讯地图\n",
        "6. 腾讯影像\n",
        "===================\n",
        )
mapIndex = input("请选择下载的地图类型：")
print (
        " ===================\n",
        "开始下载","级别为：",zoomLevel,"的地图\n",
        "===================\n",
        )
if __name__ == '__main__':
    x = getpic( float(leftTop.split(",")[0]), float(leftTop.split(",")[1]),
                float(rightBottom.split(",")[0]), float(rightBottom.split(",")[1]),
                int(zoomLevel),
                source = maps[str(mapIndex)][0],
                style=maps[str(mapIndex)][1],
                outfile="index_1.jpg")
    file_out(x,"index_1.txt","wgs")
